package com.wuhenzhizao.sku.widget;

import com.wuhenzhizao.sku.utils.AttrUtil;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * 属性流布局
 *
 * @author wuhenzhizao
 * @since 2017-08-02
 */
public class FlowLayout1 extends ComponentContainer implements Component.EstimateSizeListener, ComponentContainer.ArrangeListener {
    private static final String LOG_TAG = FlowLayout1.class.getSimpleName();
    private static final float NUM = 0.5f;

    /**
     * Special value for the child view spacing.
     * SPACING_AUTO means that the actual spacing is calculated according to the size of the
     * container and the number of the child views, so that the child views are placed evenly in
     * the container.
     */
    public static final int SPACING_AUTO = -65536;

    /**
     * Special value for the horizontal spacing of the child views in the last row
     * SPACING_ALIGN means that the horizontal spacing of the child views in the last row keeps
     * the same with the spacing used in the row above. If there is only one row, this value is
     * ignored and the spacing will be calculated according to childSpacing.
     */
    public static final int SPACING_ALIGN = -65537;

    private static final int SPACING_UNDEFINED = -65538;

    private static final int UNSPECIFIED_GRAVITY = -1;

    private static final int ROW_VERTICAL_GRAVITY_AUTO = -65536;

    private static final boolean DEFAULT_FLOW = true;
    private static final int DEFAULT_CHILD_SPACING = 0;
    private static final int DEFAULT_CHILD_SPACING_FOR_LAST_ROW = SPACING_UNDEFINED;
    private static final float DEFAULT_ROW_SPACING = 0;
    private static final boolean DEFAULT_RTL = false;
    private static final int DEFAULT_MAX_ROWS = Integer.MAX_VALUE;

    private boolean mFlow = DEFAULT_FLOW;
    private int mChildSpacing = DEFAULT_CHILD_SPACING;
    private int mMinChildSpacing = DEFAULT_CHILD_SPACING;
    private int mChildSpacingForLastRow = DEFAULT_CHILD_SPACING_FOR_LAST_ROW;
    private float mRowSpacing = DEFAULT_ROW_SPACING;
    private float mAdjustedRowSpacing = DEFAULT_ROW_SPACING;
    private boolean mRtl = DEFAULT_RTL;
    private int mMaxRows = DEFAULT_MAX_ROWS;
    private int mGravity = UNSPECIFIED_GRAVITY;
    private int mRowVerticalGravity = ROW_VERTICAL_GRAVITY_AUTO;
    private String mRowVerticalGravityStr = null;
    private int mExactMeasuredHeight;

    private List<Float> mHorizontalSpacingForRow = new ArrayList<>();
    private List<Integer> mHeightForRow = new ArrayList<>();
    private List<Integer> mWidthForRow = new ArrayList<>();
    private List<Integer> mChildNumForRow = new ArrayList<>();

    /**
     * 构造
     *
     * @param context
     */
    public FlowLayout1(Context context) {
        this(context, null);
    }

    /**
     * 构造
     *
     * @param context
     * @param attrs
     */
    public FlowLayout1(Context context, AttrSet attrs) {
        super(context, attrs);

        try {
            // 是否是流式布局 flRtl "true" 流式布局，反之将所有子 view 放在一行中 flMaxRows
            mFlow = AttrUtil.getBooleanValue(attrs, "flow", DEFAULT_FLOW);
            // 子 view 间距
            try {
                mChildSpacing = AttrUtil.getDimension(attrs, "childSpacing", DEFAULT_CHILD_SPACING);
            } catch (NumberFormatException e) {
                mChildSpacing = AttrUtil.getDimension(attrs, "childSpacing", (int) dpToPx(DEFAULT_CHILD_SPACING));
            }
            // 最后一行的布局样式
            try {
                mChildSpacingForLastRow = AttrUtil.getIntegerValue(attrs, "childSpacingForLastRow", SPACING_UNDEFINED);
            } catch (NumberFormatException e) {
                mChildSpacingForLastRow = AttrUtil.getDimension(attrs, "childSpacingForLastRow", (int) dpToPx(DEFAULT_CHILD_SPACING));
            }
            // 行距
            try {
                mRowSpacing = AttrUtil.getIntegerValue(attrs, "rowSpacing", 0);
            } catch (NumberFormatException e) {
                mRowSpacing = AttrUtil.getDimension(attrs, "rowSpacing", (int) dpToPx(DEFAULT_ROW_SPACING));
            }
            // 以行数表示的FlowLayout的最大高度。
            mMaxRows = AttrUtil.getIntegerValue(attrs, "maxRows", DEFAULT_MAX_ROWS);
            // ' true '用于从右到左布局子视图。' false '从左到右布局。默认值是' false '
            mRtl = AttrUtil.getBooleanValue(attrs, "rtl", DEFAULT_RTL);

            // 每一行垂直方向的布局方式（居中，居底部）
            mRowVerticalGravityStr = AttrUtil.getStringValue(attrs,"rowVerticalGravity","center");
        } catch (Exception e) {
            e.printStackTrace();
        }

        setEstimateSizeListener(this);
        setArrangeListener(this);
    }

    /**
     * 测量
     *
     * @param width
     * @param height
     * @return boolean
     */
    @Override
    public boolean onEstimateSize(int width, int height) {

        final int widthSize = EstimateSpec.getSize(width);
        final int widthMode = EstimateSpec.getMode(width);
        final int heightSize = EstimateSpec.getSize(height);
        final int heightMode = EstimateSpec.getMode(height);

        mHorizontalSpacingForRow.clear();
        mHeightForRow.clear();
        mWidthForRow.clear();
        mChildNumForRow.clear();


        int measuredHeight = 0, measuredWidth = 0, childCount = getChildCount();

        int rowWidth = 0, maxChildHeightInRow = 0, childNumInRow = 0;

        final int rowSize = widthSize - getPaddingLeft() - getPaddingRight();

        int rowTotalChildWidth = 0;
        final boolean allowFlow = widthMode != EstimateSpec.UNCONSTRAINT && mFlow;
        final int childSpacing = mChildSpacing == SPACING_AUTO && widthMode == EstimateSpec.UNCONSTRAINT
                ? 0 : mChildSpacing;
        final float tmpSpacing = childSpacing == SPACING_AUTO ? mMinChildSpacing : childSpacing;


//        HiLog.error(logLabel, "1111111--allowFlow-" + allowFlow + "-rowSize-" + rowSize + "-rowWidth-"
//                + rowWidth + "-rowSize-" + rowSize + "-measuredHeight-" + measuredHeight
//                + "-measuredWidth-" + measuredWidth + "-maxChildHeightInRow-" + maxChildHeightInRow
//                + "-childNumInRow-" + childNumInRow + "-childSpacing-" + childSpacing );

        for (int i = 0; i < childCount; i++) {
            Component child = getComponentAt(i);
            if (child.getVisibility() == HIDE) {
                continue;
            }

            ComponentContainer.LayoutConfig childParams = child.getLayoutConfig();
            int horizontalMargin = 0, verticalMargin = 0;
            horizontalMargin = childParams.getMarginLeft() + childParams.getMarginRight();
            verticalMargin = childParams.getMarginTop() + childParams.getMarginBottom();

            int childWidth = child.getEstimatedWidth();
            int childHeight = child.getEstimatedHeight();
            if (allowFlow && rowWidth + childWidth > rowSize) { // Need flow to next row  换行
                // Save parameters for current row
                mHorizontalSpacingForRow.add(
                        getSpacingForRow(childSpacing, rowSize, rowTotalChildWidth, childNumInRow));
                mChildNumForRow.add(childNumInRow);
                mHeightForRow.add(maxChildHeightInRow);
                mWidthForRow.add(rowWidth - (int) tmpSpacing);
                if (mHorizontalSpacingForRow.size() <= mMaxRows) {
                    measuredHeight += maxChildHeightInRow;
                }
                measuredWidth = Math.max(measuredWidth, rowWidth);

                // Place the child view to next row
                childNumInRow = 1;
                rowWidth = childWidth + (int) tmpSpacing;
                rowTotalChildWidth = childWidth;
                maxChildHeightInRow = childHeight;
            } else {
                childNumInRow++;
                rowWidth += childWidth + tmpSpacing;
                rowTotalChildWidth += childWidth;
                maxChildHeightInRow = Math.max(maxChildHeightInRow, childHeight);
            }
        }

        // Measure remaining child views in the last row
        if (mChildSpacingForLastRow == SPACING_ALIGN) {
            // For SPACING_ALIGN, use the same spacing from the row above if there is more than one
            // row.
            if (mHorizontalSpacingForRow.size() >= 1) {
                mHorizontalSpacingForRow.add(
                        mHorizontalSpacingForRow.get(mHorizontalSpacingForRow.size() - 1));
            } else {
                mHorizontalSpacingForRow.add(
                        getSpacingForRow(childSpacing, rowSize, rowTotalChildWidth, childNumInRow));
            }
        } else if (mChildSpacingForLastRow != SPACING_UNDEFINED) {
            // For SPACING_AUTO and specific DP values, apply them to the spacing strategy.
            mHorizontalSpacingForRow.add(
                    getSpacingForRow(mChildSpacingForLastRow, rowSize, rowTotalChildWidth, childNumInRow));
        } else {
            // For SPACING_UNDEFINED, apply childSpacing to the spacing strategy for the last row.
            mHorizontalSpacingForRow.add(
                    getSpacingForRow(childSpacing, rowSize, rowTotalChildWidth, childNumInRow));
        }

        mChildNumForRow.add(childNumInRow);
        mHeightForRow.add(maxChildHeightInRow);
        mWidthForRow.add(rowWidth - (int) tmpSpacing);
        if (mHorizontalSpacingForRow.size() <= mMaxRows) {
            measuredHeight += maxChildHeightInRow;
        }
        measuredWidth = Math.max(measuredWidth, rowWidth);

        if (childSpacing == SPACING_AUTO) {
            measuredWidth = widthSize;
        } else if (widthMode == EstimateSpec.UNCONSTRAINT) {
            measuredWidth = measuredWidth + getPaddingLeft() + getPaddingRight();
        } else {
            measuredWidth = Math.min(measuredWidth + getPaddingLeft() + getPaddingRight(), widthSize);
        }

        measuredHeight += getPaddingTop() + getPaddingBottom();
        int rowNum = Math.min(mHorizontalSpacingForRow.size(), mMaxRows);
        float rowSpacing = mRowSpacing == SPACING_AUTO && heightMode == EstimateSpec.UNCONSTRAINT
                ? 0 : mRowSpacing;
        if (rowSpacing == SPACING_AUTO) {
            if (rowNum > 1) {
                mAdjustedRowSpacing = (float) (heightSize - measuredHeight) / (rowNum - 1);
            } else {
                mAdjustedRowSpacing = 0;
            }
            measuredHeight = heightSize;
        } else {
            mAdjustedRowSpacing = rowSpacing;
            if (rowNum > 1) {
                measuredHeight = heightMode == EstimateSpec.UNCONSTRAINT
                        ? ((int) (measuredHeight + mAdjustedRowSpacing * (rowNum - 1)))
                        : (Math.min((int) (measuredHeight + mAdjustedRowSpacing * (rowNum - 1)),
                        heightSize));
            }
        }

        mExactMeasuredHeight = measuredHeight;

        measuredWidth = widthMode == EstimateSpec.PRECISE ? widthSize : measuredWidth;
        measuredHeight = heightMode == EstimateSpec.PRECISE ? heightSize : measuredHeight;

        setEstimatedSize(measuredWidth, measuredHeight);
        return false;
    }


    /**
     * 布局
     *
     * @param left
     * @param top
     * @param right
     * @param bottom
     * @return boolean
     */
    @Override
    public boolean onArrange(int left, int top, int right, int bottom) {
        final int paddingLeft = getPaddingLeft(), paddingRight = getPaddingRight(),
                paddingTop = getPaddingTop(), paddingBottom = getPaddingBottom();

        int x = mRtl ? (getWidth() - paddingRight) : paddingLeft;
        int y = paddingTop;

        int verticalGravity = mGravity & LayoutAlignment.VERTICAL_CENTER;
        int horizontalGravity = mGravity & LayoutAlignment.LEFT;

        switch (verticalGravity) {
            case LayoutAlignment.VERTICAL_CENTER: {
                int offset = (bottom - top - paddingTop - paddingBottom - mExactMeasuredHeight) / 2;
                y += offset;
                break;
            }
            case LayoutAlignment.BOTTOM: {
                int offset = bottom - top - paddingTop - paddingBottom - mExactMeasuredHeight;
                y += offset;
                break;
            }
            default:
                break;
        }

        int horizontalPadding = paddingLeft + paddingRight, layoutWidth = right - left;
        x += getHorizontalGravityOffsetForRow(horizontalGravity, layoutWidth, horizontalPadding, 0);
        int verticalRowGravity = 0;
        if (mRowVerticalGravityStr != null && mRowVerticalGravityStr.length() > 0) {
            if (mRowVerticalGravityStr.equals("center")) {
                verticalRowGravity = LayoutAlignment.VERTICAL_CENTER;
            } else if (mRowVerticalGravityStr.equals("bottom")) {
                verticalRowGravity = LayoutAlignment.BOTTOM;
            }
        }


        int rowCount = mChildNumForRow.size(), childIdx = 0;
        for (int row = 0; row < Math.min(rowCount, mMaxRows); row++) {
            int childNum = mChildNumForRow.get(row);
            int rowHeight = mHeightForRow.get(row);
            float spacing = mHorizontalSpacingForRow.get(row);
            for (int i = 0; i < childNum && childIdx < getChildCount(); ) {
                Component child = getComponentAt(childIdx++);
                if (child.getVisibility() == HIDE) {
                    continue;
                } else {
                    i++;
                }

                ComponentContainer.LayoutConfig childParams = child.getLayoutConfig();
                int marginLeft = 0, marginTop = 0, marginBottom = 0, marginRight = 0;
                marginLeft = childParams.getMarginLeft();
                marginRight = childParams.getMarginRight();
                marginTop = childParams.getMarginTop();
                marginBottom = childParams.getMarginBottom();

                int childWidth = child.getEstimatedWidth();
                int childHeight = child.getEstimatedHeight();
                int tt = y + marginTop;
                if (verticalRowGravity == LayoutAlignment.BOTTOM) {
                    tt = y + rowHeight - marginBottom - childHeight;
                } else if (verticalRowGravity == LayoutAlignment.VERTICAL_CENTER) {
                    tt = y + marginTop + (rowHeight - marginTop - marginBottom - childHeight) / 2;
                }
                int bb = tt + childHeight;
                if (mRtl) {
                    int l1 = x - marginRight - childWidth;
                    int r1 = x - marginRight;
                    child.setComponentPosition(l1, tt, r1, bb);
                    x -= childWidth + spacing + marginLeft + marginRight;
                } else {
                    int l2 = x + marginLeft;
                    int r2 = x + marginLeft + childWidth;
                    child.setComponentPosition(l2, tt, r2, bb);
                    x += childWidth + spacing + marginLeft + marginRight;
                }
            }
            x = mRtl ? (getWidth() - paddingRight) : paddingLeft;
            x += getHorizontalGravityOffsetForRow(
                    horizontalGravity, layoutWidth, horizontalPadding, row + 1);
            y += rowHeight + mAdjustedRowSpacing;
        }

        for (int i = childIdx; i < getChildCount(); i++) {
            Component child = getComponentAt(i);
            if (child.getVisibility() == HIDE) {
                continue;
            }
            child.setComponentPosition(0, 0, 0, 0);
        }
        return false;
    }


    /**
     * 获取水平方向偏移
     *
     * @param horizontalGravity
     * @param parentWidth
     * @param horizontalPadding
     * @param row
     * @return 偏移量
     */
    private int getHorizontalGravityOffsetForRow(int horizontalGravity, int parentWidth, int horizontalPadding, int row) {
        if (mChildSpacing == SPACING_AUTO || row >= mWidthForRow.size()
                || row >= mChildNumForRow.size() || mChildNumForRow.get(row) <= 0) {
            return 0;
        }

        int offset = 0;
        switch (horizontalGravity) {
            case LayoutAlignment.HORIZONTAL_CENTER:
                offset = (parentWidth - horizontalPadding - mWidthForRow.get(row)) / 2;
                break;
            case LayoutAlignment.RIGHT:
                offset = parentWidth - horizontalPadding - mWidthForRow.get(row);
                break;
            default:
                break;
        }
        return offset;
    }

//    @Override
//    protected LayoutParams generateLayoutParams(LayoutParams p) {
//        return new MarginLayoutParams(p);
//    }
//
//
//    @Override
//    public LayoutParams generateLayoutParams(AttributeSet attrs) {
//        return new MarginLayoutParams(getContext(), attrs);
//    }

    /**
     * Returns whether to allow child views flow to next row when there is no enough space.
     *
     * @return Whether to flow child views to next row when there is no enough space.
     */
    public boolean isFlow() {
        return mFlow;
    }

    /**
     * Sets whether to allow child views flow to next row when there is no enough space.
     *
     * @param flow true to allow flow. false to restrict all child views in one row.
     */
    public void setFlow(boolean flow) {
        mFlow = flow;
        postLayout();
    }

    /**
     * Returns the horizontal spacing between child views.
     *
     * @return The spacing, either {@link FlowLayout1#SPACING_AUTO}, or a fixed size in pixels.
     */
    public int getChildSpacing() {
        return mChildSpacing;
    }

    /**
     * Sets the horizontal spacing between child views.
     *
     * @param childSpacing The spacing, either {@link FlowLayout1#SPACING_AUTO}, or a fixed size in
     *                     pixels.
     */
    public void setChildSpacing(int childSpacing) {
        mChildSpacing = childSpacing;
        postLayout();
    }

    /**
     * Returns the horizontal spacing between child views of the last row.
     *
     * @return The spacing, either {@link FlowLayout1#SPACING_AUTO},
     * {@link FlowLayout1#SPACING_ALIGN}, or a fixed size in pixels
     */
    public int getChildSpacingForLastRow() {
        return mChildSpacingForLastRow;
    }

    /**
     * Sets the horizontal spacing between child views of the last row.
     *
     * @param childSpacingForLastRow The spacing, either {@link FlowLayout1#SPACING_AUTO},
     *                               {@link FlowLayout1#SPACING_ALIGN}, or a fixed size in pixels
     */
    public void setChildSpacingForLastRow(int childSpacingForLastRow) {
        mChildSpacingForLastRow = childSpacingForLastRow;
        postLayout();
    }

    /**
     * Returns the vertical spacing between rows.
     *
     * @return The spacing, either {@link FlowLayout1#SPACING_AUTO}, or a fixed size in pixels.
     */
    public float getRowSpacing() {
        return mRowSpacing;
    }

    /**
     * Sets the vertical spacing between rows in pixels. Use SPACING_AUTO to evenly place all rows
     * in vertical.
     *
     * @param rowSpacing The spacing, either {@link FlowLayout1#SPACING_AUTO}, or a fixed size in
     *                   pixels.
     */
    public void setRowSpacing(float rowSpacing) {
        mRowSpacing = rowSpacing;
        postLayout();
    }

    /**
     * Returns the maximum number of rows of the FlowLayout.
     *
     * @return The maximum number of rows.
     */
    public int getMaxRows() {
        return mMaxRows;
    }

    /**
     * Sets the height of the FlowLayout to be at most maxRows tall.
     *
     * @param maxRows The maximum number of rows.
     */
    public void setMaxRows(int maxRows) {
        mMaxRows = maxRows;
        postLayout();
    }

    /**
     * 展示位置
     *
     * @param gravity
     */
    public void setGravity(int gravity) {
        if (mGravity != gravity) {
            mGravity = gravity;
            postLayout();
        }
    }

    /**
     * 设置纵向位置
     *
     * @param rowVerticalGravity
     */
    public void setRowVerticalGravity(int rowVerticalGravity) {
        if (mRowVerticalGravity != rowVerticalGravity) {
            mRowVerticalGravity = rowVerticalGravity;
            postLayout();
        }
    }

    /**
     * 布局从右到左
     *
     * @return boolean
     */
    @Override
    public boolean isRtl() {
        return mRtl;
    }

    /**
     * 设置布局从右到左
     * @param rtl
     */
    public void setRtl(boolean rtl) {
        mRtl = rtl;
        postLayout();
    }

    /**
     * 获取child间距
     *
     * @return int
     */
    public int getMinChildSpacing() {
        return mMinChildSpacing;
    }

    /**
     * 设置最小子集间距
     * @param minChildSpacing
     */
    public void setMinChildSpacing(int minChildSpacing) {
        this.mMinChildSpacing = minChildSpacing;
        postLayout();
    }

    /**
     * 获取行数
     *
     * @return int
     */
    public int getRowsCount() {
        return mChildNumForRow.size();
    }

    /**
     * 获取行距
     *
     * @param spacingAttribute
     * @param rowSize
     * @param usedSize
     * @param childNum
     * @return float
     */
    private float getSpacingForRow(int spacingAttribute, int rowSize, int usedSize, int childNum) {
        float spacing;
        if (spacingAttribute == SPACING_AUTO) {
            if (childNum > 1) {
                spacing = (float) (rowSize - usedSize) / (childNum - 1);
            } else {
                spacing = 0;
            }
        } else {
            spacing = spacingAttribute;
        }
        return spacing;
    }

    /**
     * px转换
     *
     * @param dpValue
     * @return float
     */
    private float dpToPx(float dpValue) {
        Optional<Display> defaultDisplay = DisplayManager.getInstance().getDefaultDisplay(this.getContext());
        DisplayAttributes attributes = defaultDisplay.get().getAttributes();
        float scale = attributes.densityPixels;
        return (float) (dpValue * scale + NUM);
    }



}
